﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using GeneralTool.CoreLibrary.Attributes;
using GeneralTool.CoreLibrary.Extensions;
using GeneralTool.CoreLibrary.Interfaces;
using GeneralTool.CoreLibrary.Models;
using GeneralTool.CoreLibrary.SocketLib;

namespace GeneralTool.CoreLibrary.TaskLib
{
    /// <summary>
    /// 包服务
    /// </summary>
    public class PackageServerStation : ServerStationBase
    {
        private TcpListener listener;
        private CancellationTokenSource tokenSource = new CancellationTokenSource();
        /// <summary>
        /// 
        /// </summary>
        public IJsonConvert JsonConvert { get; set; }

        private readonly Dictionary<Type, PackageConverter> converters;
        private readonly string typeFullName;

        /// <summary>
        /// 使用的编码格式
        /// </summary>
        private Encoding encoding = Encoding.UTF8;

        /// <summary>
        /// 已连接的Socket
        /// </summary>
        public ConcurrentDictionary<string, Socket> CurrentSockets = new ConcurrentDictionary<string, Socket>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="log">日志组件</param>
        /// <param name="jsonConvert">json组件</param>
        /// <param name="encoding">编码格式</param>
        public PackageServerStation(ILog log, IJsonConvert jsonConvert, Encoding encoding = null, PackageHelper packageHelper = null) : base(log)
        {
            this.typeFullName = this.GetType().FullName;
            if (jsonConvert == null) jsonConvert = new BaseJsonCovert();
            this.JsonConvert = jsonConvert;
            if (encoding == null) encoding = Encoding.UTF8;
            this.encoding = encoding;
            if (packageHelper == null) packageHelper = new PackageHelper(jsonConvert, encoding);
            this.converters = packageHelper.GetConverters();
        }


        /// <inheritdoc/>
        public override bool Close()
        {
            try
            {
                this.tokenSource?.Cancel();
                if (this.listener == null) return true;
                try
                {
                    this.listener.Stop();


                    foreach (KeyValuePair<string, Socket> item in CurrentSockets)
                    {
                        try
                        {
                            item.Value.Disconnect(false);
                            item.Value.Close();
                            item.Value.Dispose();
                        }
                        catch
                        {

                        }
                    }

                    CurrentSockets.Clear();

                    this.listener.Server.Dispose();
                }
                catch
                {

                }
                this.listener?.Stop();
            }
            catch (Exception ex)
            {
                this.Log.Fail(ex);
            }
            return true;
        }

        /// <inheritdoc/>
        public override bool Start(string ip, int port)
        {
            this.tokenSource = new CancellationTokenSource();
            this.listener = new TcpListener(IPAddress.Parse(ip), port);
            this.listener.Start();
            this.listener.BeginAcceptTcpClient(this.AcceptTcpClient, null);
            return true;
        }

        private void AcceptTcpClient(IAsyncResult ar)
        {
            try
            {
                var tcpClient = this.listener.EndAcceptTcpClient(ar);
                this.Log.Info($"获取到客户端连接:{tcpClient.Client.RemoteEndPoint}");
                this.CurrentSockets.TryAdd(tcpClient.Client.RemoteEndPoint + "", tcpClient.Client);
                new Thread(ReceiveTcpClient) { IsBackground = true }.Start(tcpClient);
                this.listener.BeginAcceptTcpClient(this.AcceptTcpClient, null);
            }
            catch (ObjectDisposedException) { }
            catch (Exception ex)
            {
                this.Log.Error($"已断开:{ex.Message}");
                this.Log.Fail(ex);
            }
        }

        private void CloseClient(TcpClient client)
        {
            try
            {
                //断开连接
                var socket = client?.Client;
                this.CurrentSockets.TryRemove(socket.RemoteEndPoint + "", out _);

                socket?.Close();
                socket?.Dispose();
            }
            catch
            {

            }
        }

        private void ReceiveTcpClient(object obj)
        {
            //一个连接只处理当前连接的事情,不论长连接或短连接
            var client = obj as TcpClient;
            var remoteStr = client.Client.RemoteEndPoint.ToString();
            var stream = client.GetStream();
            var watch = new Stopwatch();
            while (!this.tokenSource.IsCancellationRequested)
            {
                if (!client.Client.IsClientConnected(client.Client.LocalEndPoint + "", client.Client.RemoteEndPoint + ""))
                {
                    this.CloseClient(client);
                    break;
                }

                //获取信息
                //获取长度
                watch.Restart();
                var dataLenBuffer = default(byte[]);
                try
                {
                    dataLenBuffer = stream.ReadBytes(4);
                }
                catch (Exception ex)
                {
                    this.Log.Fail(ex);
                }

                if (!client.Connected) break;

                var dataLen = BitConverter.ToInt32(dataLenBuffer, 0);

                if (dataLen == 0) continue;

                this.Log.Info($"获取到数据长度:{dataLen}");
                var dataBuffer = default(byte[]);
                try
                {
                    dataBuffer = stream.ReadBytes(dataLen);
                }
                catch (Exception ex)
                {
                    this.Log.Fail(ex);
                }

                if (!client.Connected) break;

                try
                {
                    var result = this.UnPackExecute(dataBuffer);
                    if (!client.Connected) break;
                    this.ReponseWrite(stream, "", true, result.Item1, result.Item2);
                }
                catch (Exception ex)
                {
                    this.Log.Fail(ex);
                    if (client.Connected)
                    {
                        try
                        {
                            this.ReponseWrite(stream, ex.Message, false);
                        }
                        catch (Exception error)
                        {
                            this.Log.Fail($"写入失败信息失败：{error}");
                        }
                    }
                }
                finally
                {
                    watch.Stop();
                    this.Log.Info($"调用且回复完成,耗时 : {watch.Elapsed}");
                }

            }

            this.CloseClient(client);
            this.Log.Info($"{remoteStr} 已经断开连接...");
        }

        /// <summary>
        /// 解析数据包
        /// </summary>
        /// <param name="dataBuffer"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public Tuple<Type, object> UnPackExecute(byte[] dataBuffer)
        {
            this.Log.Info($"开始解析数据,共获取数据量: {dataBuffer.Length}");
            //解析数据
            using (var ms = new MemoryStream(dataBuffer))
            {
                using (var reader = new BinaryReader(ms, this.encoding))
                {
                    //解析方法名称
                    var buffer = this.Read(reader);
                    var methodUrl = this.encoding.GetString(buffer);

                    //查看方法的参数数量(以支持方法重载,名称,需要以,号分割)
                    buffer = this.Read(reader);
                    var parameterStr = this.encoding.GetString(buffer);

                    this.Log.Info($"获取到方法: {methodUrl}({parameterStr})");

                    KeyValuePair<TaskKey, RequestAddressItem> first = default;
                    var parameters = default(ParameterInfo[]);
                    foreach (var item in this.RequestRoute)
                    {
                        if (item.Key.Name != methodUrl)
                            continue;

                        parameters = item.Value.MethodInfo.GetParameters();
                        string mps = string.Join(",", parameters.Select(p => p.Name));
                        if (mps != parameterStr) continue;

                        first = item;
                        break;
                    }

                    if (string.IsNullOrWhiteSpace(first.Key.Name))
                        throw new Exception($"没有找到方法:{methodUrl}({parameterStr})");

                    if (first.Value.Target is BaseTaskInvoke taskInvoke)
                    {
                        taskInvoke.IsAPInvoke = true;
                        taskInvoke.InvokeTypeFullName = this.typeFullName;
                    }

                    var array = new object[parameters.Length];

                    foreach (var parameterInfo in parameters)
                    {
                        //如果有,则转换
                        try
                        {
                            var wa = parameterInfo.GetCustomAttribute<WaterMarkAttribute>();
                            var parameterType = parameterInfo.ParameterType;
                            if (wa != null)
                            {
                                //if (wa.IsJson)
                                //{
                                //    parameterType = typeof(object);//让其使用object转换器,该转换器内部实现的是json转换
                                //}
                            }

                            array[parameterInfo.Position] = this.ParseValue(reader, parameterType);
                        }
                        catch (Exception ex)
                        {
                            this.Log.Fail(ex);
                            throw new Exception($"参数 {parameterInfo.Name} 转换成 {parameterInfo.ParameterType} 失败:{ex.GetInnerExceptionMessage()}");
                        }
                    }


                    this.Log.Info("参数转换完成,开始执行...");


                    object result = null;
                    var method = first.Value.MethodInfo;
                    try
                    {
                        result = method.Invoke(first.Value.Target, array);
                    }
                    catch (Exception ex)
                    {
                        this.Log.Fail(ex);
                        throw new Exception($"方法调用出现异常:{ex.GetInnerExceptionMessage("")}");
                    }

                    var returnType = method.ReturnType;
                    try
                    {
                        if (result is Task task)
                        {
                            this.Log.Info("异步方法...");
                            CancellationToken token = default;
                            if (first.Value.Target is BaseTaskInvoke i)
                            {
                                token = i.TokenSource == null ? default : i.TokenSource.Token;

                                if (token.IsCancellationRequested)
                                {
                                    i.TokenSource = new CancellationTokenSource();
                                    token = i.TokenSource.Token;
                                }
                            }

                            task.Wait(token);
                            result = task.GetType().GetProperty("Result").GetValue(task);
                            if (returnType.GenericTypeArguments.Length > 0)
                                returnType = returnType.GenericTypeArguments[0];
                            else
                                returnType = typeof(void);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Fail($"客户端调用服务方法执行发生错误:{ex}");

                        throw new Exception("异步方法调用错误:" + ex.GetInnerExceptionMessage(""));
                    }

                    this.Log.Info($"执行完成,获取到结果: {result}");

                    //返回结果
                    return new Tuple<Type, object>(returnType, result);
                }
            }
        }

        private byte[] Read(BinaryReader reader)
        {
            var len = reader.ReadInt32();
            var buffer = reader.ReadBytes(len);
            return buffer;
        }

        private object ParseValue(BinaryReader reader, Type parameterType)
        {
            var buffer = this.Read(reader); //读取当前参数的字节数组

            if (this.converters.TryGetValue(parameterType, out var value))
            {
                return value.BufferToObjectFunc(buffer, parameterType);
            }
            else if (parameterType.IsEnum)
            {
                try
                {
                    return Enum.Parse(parameterType, encoding.GetString(buffer));
                }
                catch
                {
                    return BitConverter.ToInt32(buffer, 0);
                }
            }

            //转为string
            return this.converters[typeof(object)].BufferToObjectFunc(buffer, parameterType);
        }

        private void ReponseWrite(NetworkStream stream, string msg, bool success, Type resultType = null, object result = null)
        {
            this.Log.Info($"准备返回客户端信息,执行是否成功->{success},错误消息->{msg},执行结果->{result}");
            using (var ms = new MemoryStream())
            {
                using (var binary = new BinaryWriter(ms, System.Text.Encoding.UTF8))
                {
                    // 注意,目前只能发送int32最大数据量,如果需要更大的,则需要改为long,但对于接口来说是OK的
                    var len = 0;
                    binary.Write(len);//占个位置
                    binary.Write(success);
                    var msgBuffer = this.converters[typeof(string)].ObjectToBufferFunc(msg);
                    binary.Write(msgBuffer.Length);
                    binary.Write(msgBuffer);

                    if (resultType == typeof(void) || resultType == null)
                    {
                        //result = "None"; //给个None
                        resultType = typeof(string);
                    }

                    var resultBuffer = new byte[0];
                    if (this.converters.TryGetValue(resultType, out var action))
                    {
                        resultBuffer = action.ObjectToBufferFunc(result);
                    }
                    else
                    {
                        resultBuffer = this.converters[typeof(object)].ObjectToBufferFunc(result);
                    }

                    //写入返回值类型
                    var resultTypeBuffer = this.encoding.GetBytes(resultType.FullName);
                    binary.Write(resultTypeBuffer.Length);
                    binary.Write(resultTypeBuffer);

                    //写入返回值数据
                    binary.Write(resultBuffer.Length);
                    binary.Write(resultBuffer);


                    ms.Seek(0, SeekOrigin.Begin);
                    binary.Write((int)(ms.Length - 4));
                }
                //写入流
                var buffer = ms.ToArray();
                stream.Write(buffer, 0, buffer.Length);
                this.Log.Info($"返回客户端消息完成,共发送字节数: {buffer.Length}");
            }
        }

        /// <summary>
        /// 协议调用说明
        /// </summary>
        public static string Remark
        {
            get
            {
                var builder = new StringBuilder();

                builder.AppendLine("本服务的调用规则如下:");
                builder.AppendLine("说明:");
                builder.AppendLine("\tBool类型均为1字节,0代表False,1代表True");
                builder.AppendLine("\t数值类型的字节数组均为小端排序,根据数值的长度不同,字节数组的长度也不同,例如32位数值(int32)为4字节长度,而8位数值(byte)为1字节长度");
                builder.AppendLine("\t所有的字符串均默认使用UTF8编码,转换的时候请与文档先确认编码格式");

                builder.AppendLine("1.Request请求:");
                builder.AppendLine("\t1.1 先写入 Controller/Method 即要调用的方法,将方法字符串以UTF8编码为字节数组后,将字节数组长度转为以小端排序的4字节长度字节数组,将长度数组与方法字节数组依次压入你的缓存中");
                builder.AppendLine("\t1.2 写入要调用方法的参数列表,例如方法的参数有两个 name与age ,则需要先将参数以,号连接,即 name,age 之后以UTF8编码为字节数组后,将字节数组长度转为以小端排序的4字节长度字节数组,将长度数组与参数字节数组依次压入你的缓存中");
                builder.AppendLine("\t1.3 写入各个参数的值,循环你的参数值列表,将对应的值转为字节数组后,将值字节数组长度转为以小端排序的4字节长度字节数组,将长度数组与值字节数组依次压入你的缓存中,如果没有参数,也需要写入空的,请注意,字符串与数值或布尔类型的值转换的字节数组长度是不一样,例如32位整型转出的字节数组,应该是小端排序的4个字节长度,而布尔类型应该为1个字节长度,且0代表False,1代表True");
                builder.AppendLine("\t1.4 向服务端socket发送你的缓存字节数组总长度,该长度应为小端排序的32位整型转换的字节数组,再发送你的缓存字节数组");

                builder.AppendLine("2.Reponse接收:");
                builder.AppendLine("\t2.1 先获取4字节数组,即接下来整个返回的数据长度,以小端排序转为32位整型数值");
                builder.AppendLine("\t2.2 根据2.1中的长度从socket中获取剩下的所有的字节数组");
                builder.AppendLine("\t2.3 第1个字节为代表方法是否调用成功,0-False,1-True");
                builder.AppendLine("\t2.4 再次获取4个长字的字节数组转为int32,即msg的字节数组长度,使用UTF8转为string类型,即方法如果未调用成功的具体错误信息,注意,就算方法调用成功,此步骤不可少");
                builder.AppendLine("\t2.5 再次获取4个长字的字节数组转为int32,即返回值的字节数组长度,根据文档给予的返回值类型,将取出的返回值字节数组转化为对应的数据");
                return builder.ToString();
            }
        }
    }
}
